package filesystem;

import java.awt.BorderLayout;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.JFileChooser;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.filechooser.FileSystemView;
import javax.swing.filechooser.FileView;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import org.lex.io.filesystem.LocalFileView;


public class DirectoryChooserPanel extends JPanel {
	/** Identifies directory change. */
	public static final String DIRECTORY_CHANGED_PROPERTY = "directoryChanged";

	private JTree tree;
	private JScrollPane scrollPane;
	//
	private FileView fileView = new LocalFileView();
	private File directory = new File(".");

	public DirectoryChooserPanel() {
		// construct ui
		super(new BorderLayout());
		this.add(this.getTreeScrollPane(), BorderLayout.CENTER);
		// ========================================================
		this.findFile(directory, true, false);
	}

	public File getDirectory() {
		return directory;
	}

	/**
	 * ����Ӧ����һЩ���м��(==null, exitst(), isDirectory())
	 * ���ң�setDirectory(File)Ӧ�ó���һЩ����¼�
	 */
	public void setDirectory(File directory) {
		this.directory = directory;
	}

	public FileView getFileView() {
		return fileView;
	}

	public void setFileView(FileView fileView) {
		this.fileView = fileView;
	}

	private JScrollPane getTreeScrollPane() {
		if (null == this.scrollPane) {
			scrollPane = new JScrollPane(this.getTree());
		}
		return scrollPane;
	}

	protected JTree getTree() {
		if (null == tree) {
			tree = new JTree() {
				@Override
				public String getToolTipText(MouseEvent event) {
					String tip = null;
					TreePath path = this.getPathForLocation(event.getX(), event.getY());
					if (path != null && path.getLastPathComponent() instanceof FileNode) {
						FileNode node = (FileNode) path.getLastPathComponent();
						String typeDescription = getFileView().getTypeDescription(node.getFile());
						if (typeDescription == null || typeDescription.length() == 0) {
							tip = node.toString();
						} else {
							tip = node.toString() + " - " + typeDescription;
						}
					}
					if (tip == null) {
						return super.getToolTipText(event);
					} else {
						return tip;
					}
				}
			};
			tree.setModel(new FileSystemTreeModel());
			tree.addTreeExpansionListener(new TreeExpansion());
			tree.addTreeSelectionListener(new SelectionListener());
			tree.setCellRenderer(new FileSystemTreeRenderer(this));
			tree.setRootVisible(false);
			tree.setShowsRootHandles(false);
			tree.setToolTipText("");
		}
		return tree;
	}

	/**
	 * Ensures the file is visible, tree expanded and optionally selected
	 * 
	 * @param fileToLocate
	 * @param selectFile
	 */
	private void findFile(File fileToLocate, boolean selectFile, boolean reload) {
		if (fileToLocate == null || false == fileToLocate.isDirectory())
			return;

		// build the canonical path so we can
		// navigate the tree model to find the node
		File file = null;
		try {
			file = fileToLocate.getCanonicalFile();
		} catch (Exception e) {
			return;
		}

		// split the full path into individual files to
		// locate them in the tree model
		FileSystemView fsv = FileSystemView.getFileSystemView();
		List<File> filePath = new ArrayList<File>();
		while (null != file) {
			filePath.add(file);
			file = fsv.getParentDirectory(file);
		}
		Collections.reverse(filePath);

		// start from the root
		List<TreeNode> nodePath = new ArrayList<TreeNode>();
		TreeNode root = (TreeNode) tree.getModel().getRoot();
		nodePath.add(root);

		// ...and go through the tree model to find the files. Stop as
		// soon as path is completely found or if one of the files in the path
		// is not found.
		boolean found = true;
		TreeNode current = null;
		while (filePath.size() > 0 && found) {
			found = false;
			for (int i = 0, c = root.getChildCount(); i < c; i++) {
				current = root.getChildAt(i);
				File f = ((FileNode) current).getFile();
				if (filePath.get(0).equals(f)) {
					nodePath.add(current);
					filePath.remove(0);
					root = current;
					found = true;
					break;
				}
			}
		}

		// select the path we found, it may be the file we were looking
		// for or a subpath only
		TreePath pathToSelect = new TreePath(nodePath.toArray());
		if (pathToSelect.getLastPathComponent() instanceof FileNode && reload) {
			((FileNode) (pathToSelect.getLastPathComponent())).clear();
		}

		if (selectFile) {
			tree.expandPath(pathToSelect);
			tree.setSelectionPath(pathToSelect);
		}

		tree.makeVisible(pathToSelect); // scrollPathToVisible(pathToSelect);
	}

	class ChangeListener implements PropertyChangeListener {

		public void propertyChange(PropertyChangeEvent evt) {

			if (JFileChooser.DIRECTORY_CHANGED_PROPERTY.equals(evt.getPropertyName())) {
				findFile(directory, false, false);
			}
		}
	}

	class SelectionListener implements TreeSelectionListener {

		public void valueChanged(TreeSelectionEvent e) {
			// the current directory is the one currently selected
			TreePath currentDirectoryPath = tree.getSelectionPath();
			if (currentDirectoryPath != null) {
				File currentDirectory = ((FileNode) currentDirectoryPath.getLastPathComponent()).getFile();
				setDirectory(currentDirectory);
			}
		}
	}

	/**
	 * Listens on nodes being opened and preload their children to get better UI
	 * experience (GUI should be more responsive and empty nodes discovered
	 * automatically).
	 */
	private class TreeExpansion implements TreeExpansionListener {

		public void treeCollapsed(TreeExpansionEvent event) {
		}

		public void treeExpanded(TreeExpansionEvent event) {
			// ensure children gets expanded later
			if (event.getPath() != null) {
				Object lastElement = event.getPath().getLastPathComponent();
				if (lastElement instanceof FileNode) {
					if (((FileNode) lastElement).hasLoaded()) {
						// enqueueChildren( (FileTreeNode) lastElement );
					}
				}
			}
		}
	}

}
